package org.japo.java.jdbc.samples;

import java.awt.Color;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeListener;
import org.japo.java.entities.Modulo;

/**
 *
 * @author José A. Pacheco Ondoño - joanpaon@gmail.com
 */
public class GestionModulos extends javax.swing.JFrame {

    // Nombre de la base de datos
    private final String BD = "Agenda";

    // SGBD soportador - Selección del SGBD activo
    private final String PRODUCTO1 = "Access";   // Access
    private final String PRODUCTO2 = "MySQL";    // MySQL
    private final String PRODUCTO_ACTIVO = PRODUCTO2;

    // Referencias para la gestión de la base de datos
    private Connection con = null;
    private Statement stmt = null;
    private ResultSet rs = null;

    // Referencia al modelo de enlace con el interfaz
    private Modulo modulo;

    // Semáforos de validación
    private boolean idOK;
    private boolean nombreOK;
    private boolean acronimoOK;
    private boolean codigoOK;

    // Expresiones regulares - Coherentes con la estructura de la BD
    private final String ER_ID = "0|([1-9][0-9]{0,10})";                 // Id
    private final String ER_ACRONIMO = "[A-Z]{1,10}";                    // Acrónimo
    private final String ER_NOMBRE = "[a-zA-ZáéíóúüñÁÉÍÓÚÜÑ ]{1,50}";    // Nombre
    private final String ER_CODIGO = "[A-Z0-9]{1,10}";                   // Código

    public GestionModulos() {
        iniciarAnterior();
        initComponents();
        iniciarPosterior();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        bindingGroup = new org.jdesktop.beansbinding.BindingGroup();

        btgCurso = new javax.swing.ButtonGroup();
        pnlDatos = new javax.swing.JPanel();
        lblId = new javax.swing.JLabel();
        lblAcronimo = new javax.swing.JLabel();
        lblNombre = new javax.swing.JLabel();
        lblCodigo = new javax.swing.JLabel();
        lblHoras = new javax.swing.JLabel();
        LblCurso = new javax.swing.JLabel();
        txfId = new javax.swing.JTextField();
        txfAcronimo = new javax.swing.JTextField();
        txfNombre = new javax.swing.JTextField();
        txfCodigo = new javax.swing.JTextField();
        lblHorasDato = new javax.swing.JLabel();
        rbtCurso1 = new javax.swing.JRadioButton();
        rbtCurso2 = new javax.swing.JRadioButton();
        sldHoras = new javax.swing.JSlider();
        pnlControl = new javax.swing.JPanel();
        btnIni = new javax.swing.JButton();
        btnAnt = new javax.swing.JButton();
        btnSig = new javax.swing.JButton();
        btnFin = new javax.swing.JButton();
        sldRegAct = new javax.swing.JSlider();
        lblRegAct = new javax.swing.JLabel();
        lblNumReg = new javax.swing.JLabel();
        btnIns = new javax.swing.JButton();
        btnAct = new javax.swing.JButton();
        btnBor = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Gestor de módulos");
        setResizable(false);
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });

        pnlDatos.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Panel de control", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("SansSerif", 0, 14))); // NOI18N

        lblId.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        lblId.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
        lblId.setText("Id");

        lblAcronimo.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        lblAcronimo.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
        lblAcronimo.setText("Acrónimo");

        lblNombre.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        lblNombre.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
        lblNombre.setText("Nombre");

        lblCodigo.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        lblCodigo.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
        lblCodigo.setText("Código");

        lblHoras.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        lblHoras.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
        lblHoras.setText("Horas");

        LblCurso.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        LblCurso.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
        LblCurso.setText("Curso");

        txfId.setFont(new java.awt.Font("SansSerif", 0, 14)); // NOI18N

        org.jdesktop.beansbinding.Binding binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${modulo.id}"), txfId, org.jdesktop.beansbinding.BeanProperty.create("text_ON_FOCUS_LOST"), "id");
        bindingGroup.addBinding(binding);

        txfId.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                txfIdFocusGained(evt);
            }
            public void focusLost(java.awt.event.FocusEvent evt) {
                txfIdFocusLost(evt);
            }
        });

        txfAcronimo.setFont(new java.awt.Font("SansSerif", 0, 14)); // NOI18N

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${modulo.acronimo}"), txfAcronimo, org.jdesktop.beansbinding.BeanProperty.create("text_ON_FOCUS_LOST"), "acronimo");
        bindingGroup.addBinding(binding);

        txfAcronimo.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                txfAcronimoFocusGained(evt);
            }
            public void focusLost(java.awt.event.FocusEvent evt) {
                txfAcronimoFocusLost(evt);
            }
        });

        txfNombre.setFont(new java.awt.Font("SansSerif", 0, 14)); // NOI18N
        txfNombre.setAutoscrolls(false);
        txfNombre.setMaximumSize(new java.awt.Dimension(6, 20));
        txfNombre.setPreferredSize(new java.awt.Dimension(20, 20));

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${modulo.nombre}"), txfNombre, org.jdesktop.beansbinding.BeanProperty.create("text_ON_FOCUS_LOST"), "nombre");
        bindingGroup.addBinding(binding);

        txfNombre.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                txfNombreFocusGained(evt);
            }
            public void focusLost(java.awt.event.FocusEvent evt) {
                txfNombreFocusLost(evt);
            }
        });

        txfCodigo.setFont(new java.awt.Font("SansSerif", 0, 14)); // NOI18N
        txfCodigo.setAutoscrolls(false);

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${modulo.codigo}"), txfCodigo, org.jdesktop.beansbinding.BeanProperty.create("text_ON_FOCUS_LOST"), "");
        bindingGroup.addBinding(binding);

        txfCodigo.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                txfCodigoFocusGained(evt);
            }
            public void focusLost(java.awt.event.FocusEvent evt) {
                txfCodigoFocusLost(evt);
            }
        });

        lblHorasDato.setBackground(new java.awt.Color(255, 255, 255));
        lblHorasDato.setFont(new java.awt.Font("SansSerif", 0, 14)); // NOI18N
        lblHorasDato.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        lblHorasDato.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        lblHorasDato.setOpaque(true);

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, sldHoras, org.jdesktop.beansbinding.ELProperty.create("${value}"), lblHorasDato, org.jdesktop.beansbinding.BeanProperty.create("text"));
        bindingGroup.addBinding(binding);

        btgCurso.add(rbtCurso1);
        rbtCurso1.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        rbtCurso1.setText("Primero");

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${modulo.curso1}"), rbtCurso1, org.jdesktop.beansbinding.BeanProperty.create("selected"));
        bindingGroup.addBinding(binding);

        btgCurso.add(rbtCurso2);
        rbtCurso2.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        rbtCurso2.setText("Segundo");

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${modulo.curso2}"), rbtCurso2, org.jdesktop.beansbinding.BeanProperty.create("selected"));
        bindingGroup.addBinding(binding);

        sldHoras.setFont(new java.awt.Font("SansSerif", 0, 14)); // NOI18N
        sldHoras.setMajorTickSpacing(100);
        sldHoras.setMaximum(500);
        sldHoras.setMinorTickSpacing(100);
        sldHoras.setPaintLabels(true);
        sldHoras.setPaintTicks(true);
        sldHoras.setToolTipText("");

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${modulo.horas}"), sldHoras, org.jdesktop.beansbinding.BeanProperty.create("value"));
        bindingGroup.addBinding(binding);

        javax.swing.GroupLayout pnlDatosLayout = new javax.swing.GroupLayout(pnlDatos);
        pnlDatos.setLayout(pnlDatosLayout);
        pnlDatosLayout.setHorizontalGroup(
            pnlDatosLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlDatosLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlDatosLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(pnlDatosLayout.createSequentialGroup()
                        .addComponent(lblAcronimo)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(txfAcronimo))
                    .addGroup(pnlDatosLayout.createSequentialGroup()
                        .addComponent(lblId, javax.swing.GroupLayout.PREFERRED_SIZE, 70, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(txfId))
                    .addGroup(pnlDatosLayout.createSequentialGroup()
                        .addComponent(lblHoras)
                        .addGap(4, 4, 4)
                        .addComponent(sldHoras, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(lblHorasDato, javax.swing.GroupLayout.PREFERRED_SIZE, 44, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(pnlDatosLayout.createSequentialGroup()
                        .addComponent(lblCodigo)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(txfCodigo))
                    .addGroup(pnlDatosLayout.createSequentialGroup()
                        .addComponent(lblNombre)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(txfNombre, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                    .addGroup(pnlDatosLayout.createSequentialGroup()
                        .addComponent(LblCurso)
                        .addGap(18, 18, 18)
                        .addComponent(rbtCurso1)
                        .addGap(18, 18, 18)
                        .addComponent(rbtCurso2)
                        .addGap(0, 0, Short.MAX_VALUE)))
                .addContainerGap())
        );

        pnlDatosLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {LblCurso, lblAcronimo, lblCodigo, lblHoras, lblId, lblNombre});

        pnlDatosLayout.setVerticalGroup(
            pnlDatosLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlDatosLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlDatosLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(txfId, javax.swing.GroupLayout.PREFERRED_SIZE, 28, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(lblId))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(pnlDatosLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(txfAcronimo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(lblAcronimo))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(pnlDatosLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(lblNombre, javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(txfNombre, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(pnlDatosLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(lblCodigo)
                    .addComponent(txfCodigo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(9, 9, 9)
                .addGroup(pnlDatosLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(pnlDatosLayout.createSequentialGroup()
                        .addGroup(pnlDatosLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(sldHoras, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(lblHoras))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(pnlDatosLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(rbtCurso1)
                            .addComponent(rbtCurso2)
                            .addComponent(LblCurso)))
                    .addComponent(lblHorasDato, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)))
        );

        pnlDatosLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {rbtCurso1, rbtCurso2, sldHoras});

        pnlDatosLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {lblAcronimo, lblCodigo, lblHoras, lblHorasDato, lblId, lblNombre, txfAcronimo, txfCodigo, txfId, txfNombre});

        pnlControl.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Panel de control", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("SansSerif", 0, 14))); // NOI18N

        btnIni.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        btnIni.setText("<<");
        btnIni.setEnabled(false);
        btnIni.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnIniActionPerformed(evt);
            }
        });

        btnAnt.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        btnAnt.setText("<");
        btnAnt.setEnabled(false);
        btnAnt.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnAntActionPerformed(evt);
            }
        });

        btnSig.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        btnSig.setText(">");
        btnSig.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnSigActionPerformed(evt);
            }
        });

        btnFin.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        btnFin.setText(">>");
        btnFin.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnFinActionPerformed(evt);
            }
        });

        sldRegAct.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        sldRegAct.setMaximum(0);
        sldRegAct.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                sldRegActStateChanged(evt);
            }
        });

        lblRegAct.setBackground(new java.awt.Color(255, 255, 255));
        lblRegAct.setFont(new java.awt.Font("SansSerif", 0, 14)); // NOI18N
        lblRegAct.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        lblRegAct.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        lblRegAct.setOpaque(true);

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, sldRegAct, org.jdesktop.beansbinding.ELProperty.create("${value}"), lblRegAct, org.jdesktop.beansbinding.BeanProperty.create("text"));
        bindingGroup.addBinding(binding);

        lblNumReg.setBackground(new java.awt.Color(255, 255, 255));
        lblNumReg.setFont(new java.awt.Font("SansSerif", 0, 14)); // NOI18N
        lblNumReg.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        lblNumReg.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        lblNumReg.setOpaque(true);

        btnIns.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        btnIns.setText("Insertar");
        btnIns.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnInsActionPerformed(evt);
            }
        });

        btnAct.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        btnAct.setText("Actualizar");
        btnAct.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnActActionPerformed(evt);
            }
        });

        btnBor.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        btnBor.setText("Borrar");
        btnBor.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnBorActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout pnlControlLayout = new javax.swing.GroupLayout(pnlControl);
        pnlControl.setLayout(pnlControlLayout);
        pnlControlLayout.setHorizontalGroup(
            pnlControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlControlLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(pnlControlLayout.createSequentialGroup()
                        .addComponent(sldRegAct, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(lblRegAct, javax.swing.GroupLayout.PREFERRED_SIZE, 44, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(lblNumReg, javax.swing.GroupLayout.PREFERRED_SIZE, 44, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(pnlControlLayout.createSequentialGroup()
                        .addComponent(btnIni)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnAnt)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(pnlControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(btnIns, javax.swing.GroupLayout.PREFERRED_SIZE, 230, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(btnAct, javax.swing.GroupLayout.PREFERRED_SIZE, 230, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(btnBor, javax.swing.GroupLayout.PREFERRED_SIZE, 230, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnSig)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnFin, javax.swing.GroupLayout.PREFERRED_SIZE, 58, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(0, 0, Short.MAX_VALUE)))
                .addContainerGap())
        );

        pnlControlLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {btnAnt, btnFin, btnIni, btnSig});

        pnlControlLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {lblNumReg, lblRegAct});

        pnlControlLayout.setVerticalGroup(
            pnlControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlControlLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addGroup(pnlControlLayout.createSequentialGroup()
                        .addComponent(btnIns)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnAct)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnBor))
                    .addComponent(btnFin, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(btnSig, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(btnAnt, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(btnIni, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(pnlControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(sldRegAct, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(lblRegAct, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(lblNumReg, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pnlControlLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {lblNumReg, lblRegAct, sldRegAct});

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(pnlControl, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(pnlDatos, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(pnlDatos, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(pnlControl, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        bindingGroup.bind();

        pack();
        setLocationRelativeTo(null);
    }// </editor-fold>//GEN-END:initComponents

    private void btnAntActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAntActionPerformed
        try {
            if (rs.previous()) {
                actualizarInterfaz();
            }
        } catch (SQLException sqle) {
            System.out.println("Sig: " + sqle);
        }
    }//GEN-LAST:event_btnAntActionPerformed

    private void btnSigActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSigActionPerformed
        try {
            if (rs.next()) {
                actualizarInterfaz();
            }
        } catch (SQLException sqle) {
            System.out.println("Sig: " + sqle);
        }
    }//GEN-LAST:event_btnSigActionPerformed

    private void btnIniActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnIniActionPerformed
        try {
            if (rs.first()) {
                actualizarInterfaz();
            }
        } catch (SQLException sqle) {
            System.out.println("Ini: " + sqle);
        }
    }//GEN-LAST:event_btnIniActionPerformed

    private void btnFinActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnFinActionPerformed
        try {
            if (rs.last()) {
                actualizarInterfaz();
            }
        } catch (SQLException sqle) {
            System.out.println("Fin: " + sqle);
        }
    }//GEN-LAST:event_btnFinActionPerformed

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        cerrarBD();
    }//GEN-LAST:event_formWindowClosing

    private void sldRegActStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sldRegActStateChanged
        try {
            if (rs.absolute(sldRegAct.getValue())) {
                actualizarInterfaz();
            }
        } catch (SQLException sqle) {
            System.out.println("Sig: " + sqle);
        }
    }//GEN-LAST:event_sldRegActStateChanged

    private void btnInsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnInsActionPerformed
        // Proceso de inserción
        if (comprobarValidez()) {
            int respuesta = JOptionPane.showConfirmDialog(this,
                    "Insertar los datos actuales",
                    "Insertar registro",
                    JOptionPane.OK_CANCEL_OPTION,
                    JOptionPane.WARNING_MESSAGE);

            if (respuesta == JOptionPane.OK_OPTION) {
                try {
                    // Insertar la fila actual
                    insertarFila(rs);

                    // Actualizar interfaz
                    actualizarInterfaz();
                } catch (SQLException e) {
                    JOptionPane.showMessageDialog(this,
                            "Inserción de datos cancelada");
                }
            }
        } else {
            JOptionPane.showMessageDialog(this, "Hay datos erróneos");
        }
    }//GEN-LAST:event_btnInsActionPerformed

    private void btnActActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnActActionPerformed
        // Proceso de inserción
        if (comprobarValidez()) {
            int respuesta = JOptionPane.showConfirmDialog(this,
                    "Actualizar los datos actuales",
                    "Actualizar registro",
                    JOptionPane.OK_CANCEL_OPTION,
                    JOptionPane.WARNING_MESSAGE);

            if (respuesta == JOptionPane.OK_OPTION) {
                try {
                    // Actualizar la fila actual
                    actualizarFila(rs);

                    // Actualizar interfaz
//                    actualizarInterfaz();
                } catch (SQLException e) {
                    JOptionPane.showMessageDialog(this,
                            "Actualización de datos cancelada");
                }
            }
        } else {
            JOptionPane.showMessageDialog(this, "Hay datos erróneos");
        }
    }//GEN-LAST:event_btnActActionPerformed

    private void btnBorActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnBorActionPerformed
        // Muestra diálogo de confirmación
        int respuesta = JOptionPane.showConfirmDialog(this,
                "Borrar los datos actuales",
                "Borrar registro",
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.WARNING_MESSAGE);

        if (respuesta == JOptionPane.OK_OPTION) {
            try {
                // Borrar la fila actual
                borrarFila(rs);

                // Actualizar interfaz
                actualizarInterfaz();
            } catch (Exception e) {
                JOptionPane.showMessageDialog(this,
                        "Actualización de datos cancelada");
            }
        }
    }//GEN-LAST:event_btnBorActionPerformed

    private void txfIdFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfIdFocusGained
        // Color de la letra en modo edición
        txfId.setForeground(Color.BLACK);

        // Color de fondo de campo seleccionado
        txfId.setBackground(Color.CYAN);
    }//GEN-LAST:event_txfIdFocusGained

    private void txfAcronimoFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfAcronimoFocusGained
        // Color de la letra en modo edición
        txfAcronimo.setForeground(Color.BLACK);

        // Color de fondo de campo seleccionado
        txfAcronimo.setBackground(Color.CYAN);
    }//GEN-LAST:event_txfAcronimoFocusGained

    private void txfNombreFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfNombreFocusGained
        // Color de la letra en modo edición
        txfNombre.setForeground(Color.BLACK);

        // Color de fondo de campo seleccionado
        txfNombre.setBackground(Color.CYAN);
    }//GEN-LAST:event_txfNombreFocusGained

    private void txfCodigoFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfCodigoFocusGained
        // Color de la letra en modo edición
        txfCodigo.setForeground(Color.BLACK);

        // Color de fondo de campo seleccionado
        txfCodigo.setBackground(Color.CYAN);
    }//GEN-LAST:event_txfCodigoFocusGained

    private void txfIdFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfIdFocusLost
        // Valida el campo
        idOK = validarCampoTexto(txfId, ER_ID, "*");

        // Color de fondo de campo
        txfId.setBackground(Color.WHITE);
    }//GEN-LAST:event_txfIdFocusLost

    private void txfAcronimoFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfAcronimoFocusLost
        // Valida el campo
        acronimoOK = validarCampoTexto(txfAcronimo, ER_ACRONIMO, "*");

        // Color de fondo de campo
        txfAcronimo.setBackground(Color.WHITE);
    }//GEN-LAST:event_txfAcronimoFocusLost

    private void txfNombreFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfNombreFocusLost
        // Valida el campo
        nombreOK = validarCampoTexto(txfNombre, ER_NOMBRE, "*");

        // Color de fondo de campo
        txfNombre.setBackground(Color.WHITE);
    }//GEN-LAST:event_txfNombreFocusLost

    private void txfCodigoFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfCodigoFocusLost
        // Valida el campo 
        codigoOK = validarCampoTexto(txfCodigo, ER_CODIGO, "*");

        // Color de fondo de campo
        txfCodigo.setBackground(Color.WHITE);
    }//GEN-LAST:event_txfCodigoFocusLost

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /*
         * If Nimbus (introduced in Java SE 6) is not available, stay with the
         * default look and feel. For details see
         * http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Windows".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(GestionModulos.class
                    .getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(GestionModulos.class
                    .getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(GestionModulos.class
                    .getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(GestionModulos.class
                    .getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /*
         * Create and display the form
         */
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new GestionModulos().setVisible(true);
            }
        });
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JLabel LblCurso;
    private javax.swing.ButtonGroup btgCurso;
    private javax.swing.JButton btnAct;
    private javax.swing.JButton btnAnt;
    private javax.swing.JButton btnBor;
    private javax.swing.JButton btnFin;
    private javax.swing.JButton btnIni;
    private javax.swing.JButton btnIns;
    private javax.swing.JButton btnSig;
    private javax.swing.JLabel lblAcronimo;
    private javax.swing.JLabel lblCodigo;
    private javax.swing.JLabel lblHoras;
    private javax.swing.JLabel lblHorasDato;
    private javax.swing.JLabel lblId;
    private javax.swing.JLabel lblNombre;
    private javax.swing.JLabel lblNumReg;
    private javax.swing.JLabel lblRegAct;
    private javax.swing.JPanel pnlControl;
    private javax.swing.JPanel pnlDatos;
    private javax.swing.JRadioButton rbtCurso1;
    private javax.swing.JRadioButton rbtCurso2;
    private javax.swing.JSlider sldHoras;
    private javax.swing.JSlider sldRegAct;
    private javax.swing.JTextField txfAcronimo;
    private javax.swing.JTextField txfCodigo;
    private javax.swing.JTextField txfId;
    private javax.swing.JTextField txfNombre;
    private org.jdesktop.beansbinding.BindingGroup bindingGroup;
    // End of variables declaration//GEN-END:variables

    private Connection conectarAccess(String bd) throws Exception {
        // Conexión
        Connection c;

        // Protocolo del driver
        final String PROTOCOLO_ACCESS = "jdbc:odbc";

        // Cadena de conexión de Access
        final String CADENA_ACCESS = PROTOCOLO_ACCESS + ":" + bd;

        // Realiza la conexión
        System.out.println("Abriendo " + bd + " ...");
        c = DriverManager.getConnection(CADENA_ACCESS);
        System.out.println("Base de datos " + bd + " conectada.");

        // Devolver la conexión obtenida
        return c;
    }

    private Connection conectarMySQL(String bd) throws Exception {
        // Conexión
        Connection c;

        // Protocolo del driver
        final String PROTOCOLO_MySQL = "jdbc:mysql";

        // Servidor
        final String SERVIDOR = "localhost";
        final int PUERTO = 3306;

        // Usuario
        final String USUARIO = "usuario";
        final String PASSWORD = "usuario";

        // Cadena de conexión de MySQL
        final String CADENA_MySQL = ""
                + PROTOCOLO_MySQL + "://"
                + SERVIDOR + ":"
                + PUERTO + "/"
                + bd;

        // Realiza la conexión
        System.out.println("Abriendo " + bd + " ...");
        c = DriverManager.getConnection(CADENA_MySQL, USUARIO, PASSWORD);
        System.out.println("Base de datos " + bd + " conectada.");

        // Devolver la conexión obtenida
        return c;
    }

    private void iniciarAnterior() {
        // Instanciar el modelo de datos
        modulo = new Modulo();

        // Crear el acceso a la base de datos
        crearAccesoDatos(BD);
    }

    private void iniciarPosterior() {
        actualizarInterfaz();
    }

    private void crearAccesoDatos(String bd) {
        try {
            // Establecer conexión con la base de datos
            con = conectarBD(bd);

            // ---- CAMBIOS DE DATOS ----
            // ResultSet.TYPE_FORWARD_ONLY (*) - Indica que el objeto ResultSet se 
            //      puede recorrer unicamente hacia adelante.
            // ResultSet.TYPE_SCROLL_INSENSITIVE - Indica que el objeto ResultSet se 
            //      puede recorrer, pero en general no es sensible a los cambios en 
            //      los datos que subyacen en él.
            // ResultSet.TYPE_SCROLL_SENSITIVE - Indica que el objeto ResultSet se 
            //      puede recorrer, y además, los cambios en él repercuten
            //      en la base de datos subyacente.
            // ---- MODOS DE CONCURRENCIA ----
            // ResultSet.CONCUR_READ_ONLY (*) - Indica que en el modo de concurrencia 
            //      para el objeto ResultSet éste no puede ser actualizado.
            // ResultSet.CONCUR_UPDATABLE - Indica que en el modo de concurrencia 
            //      para el objeto ResultSet éste podria ser actualizado.
            //
            // Crear una sentencia
            stmt = con.createStatement(
                    ResultSet.TYPE_SCROLL_SENSITIVE,
                    ResultSet.CONCUR_UPDATABLE);

            // Sentencia SQL para la consulta
            final String SQL = "SELECT * FROM Modulo";

            // Obtener los resultados de la consulta
            rs = stmt.executeQuery(SQL);
        } catch (Exception e) {
            // Información de depuración
            Logger.getLogger(GestionModulos.class.getName()).log(Level.SEVERE, null, e);

            // Cierra la base de datos
            cerrarBD();

            // Finaliza el programa
            terminarPrograma();
        }
    }

    private Connection conectarBD(String bd) throws Exception {
        // Referencia a la conexión
        Connection c;

        // Conecta con la base de datos
        if (PRODUCTO_ACTIVO.equals(PRODUCTO1)) {
            c = conectarAccess(bd);
        } else if (PRODUCTO_ACTIVO.equals(PRODUCTO2)) {
            c = conectarMySQL(bd);
        } else {
            throw new Exception("No se ha seleccionado producto");
        }

        // Devuelve la conexión obtenida
        return c;
    }

    private void actualizarInterfaz() {
        try {
            // Actualizar datos interfaz
            importarModelo(modulo, rs);

            // Actualizar botones
            actualizarNavegador(rs);

            // Actualizar selector
            actualizarSelector(sldRegAct, lblNumReg, rs);
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    private void borrarFila(ResultSet rs) throws Exception {
        // Borra la fila actual
        rs.deleteRow();

        // Va a la primera fila
        rs.first();
    }

    private void importarModelo(Modulo m, ResultSet rs) throws Exception {
        // Obtener el número de filas
        int totFilas = calcularNumeroRegistros(rs);

        if (totFilas > 0) {
            // Importa los datos de la fila actual
            m.setId(rs.getInt(1) + "");
            m.setAcronimo(rs.getString(2));
            m.setNombre(rs.getString(3));
            m.setCodigo(rs.getString(4));
            m.setHoras(rs.getInt(5));
            m.setCurso1(rs.getInt(6) == 1);
            m.setCurso2(rs.getInt(6) == 2);
        } else {
            // Datos arbitrarios cuando no hay filas
            m.setId("");
            m.setAcronimo("");
            m.setNombre("");
            m.setCodigo("");
            m.setHoras(0);
            m.setCurso1(false);
            m.setCurso2(false);
        }
    }

    private void actualizarNavegador(ResultSet rs) throws Exception {
        // Obtener el número de filas
        int totFilas = calcularNumeroRegistros(rs);

        // Obtener la fila actual
        int filaAct = calcularPosicionActual(rs);

        if (totFilas == 0) {                        // Base de datos vacia
            btnAnt.setEnabled(false);
            btnIni.setEnabled(false);
            btnSig.setEnabled(false);
            btnFin.setEnabled(false);
            btnBor.setEnabled(false);
            btnAct.setEnabled(false);
        } else if (totFilas == 1) {                 // Un sólo registro
            btnAnt.setEnabled(false);
            btnIni.setEnabled(false);
            btnSig.setEnabled(false);
            btnFin.setEnabled(false);
            btnBor.setEnabled(true);
            btnAct.setEnabled(true);
        } else if (filaAct == 1) {                  // Primer registro
            btnAnt.setEnabled(false);
            btnIni.setEnabled(false);
            btnSig.setEnabled(true);
            btnFin.setEnabled(true);
            btnBor.setEnabled(true);
            btnAct.setEnabled(true);
        } else if (filaAct == totFilas) {           // Último registro
            btnAnt.setEnabled(true);
            btnIni.setEnabled(true);
            btnSig.setEnabled(false);
            btnFin.setEnabled(false);
            btnBor.setEnabled(true);
            btnAct.setEnabled(true);
        } else {                                    // Registro intermedio
            btnAnt.setEnabled(true);
            btnIni.setEnabled(true);
            btnSig.setEnabled(true);
            btnFin.setEnabled(true);
            btnBor.setEnabled(true);
            btnAct.setEnabled(true);
        }
    }

    private void actualizarSelector(JSlider sldActual, JLabel lblActual,
            ResultSet rs) throws Exception {
        // Obtener el número de filas
        int totFilas = calcularNumeroRegistros(rs);

        // Obtener la fila actual
        int filaAct = calcularPosicionActual(rs);

        // Guarda los gestores de eventos
        ChangeListener[] lista = sldActual.getChangeListeners();

        // Desconecta los ChangeListener definidos en tiempo de diseño
        // Pero no los BeanBinding - Si se desconectan todos en enlazado
        // NO FUNCIONA
        for (ChangeListener cl : lista) {
            // Obtiene la representación de texto del Listener
            String texto = cl.toString();

            // Ignora el listener si es BeanBinding
            if (!texto.contains("org.jdesktop")) {
                sldActual.removeChangeListener(cl);
            }
        }

        // Analiza el estado del deslizador
        if (totFilas > 0) {
            // Deslizador
            sldActual.setEnabled(true);
            sldActual.setMinimum(1);          // Change Listeners notificados
            sldActual.setMaximum(totFilas);   // Change Listeners notificados
            sldActual.setValue(filaAct);      // Change Listeners notificados

            // Total de registros
            lblActual.setText(totFilas + "");
        } else {
            // Deslizador
            sldActual.setEnabled(false);
            sldActual.setMinimum(0);          // Change Listeners notificados
            sldActual.setMaximum(0);          // Change Listeners notificados
            sldActual.setValue(0);            // Change Listeners notificados

            // Total de registros
            lblActual.setText("0");
        }

        // Conecta los ChangeListener desconectados
        for (ChangeListener cl : lista) {
            // Obtiene la representación de texto del Listener
            String texto = cl.toString();

            // Ignora el listener si es BeanBinding
            if (!texto.contains("org.jdesktop")) {
                sldActual.addChangeListener(cl);
            }
        }
    }

    public Modulo getModulo() {
        return modulo;
    }

    public void setModulo(Modulo modulo) {
        this.modulo = modulo;
    }

    public void cerrarBD() {
        // Cierra el ResultSet
        try {
            if (rs != null) {
                rs.close();
                rs = null;
            }
        } catch (SQLException | NullPointerException e) {
            Logger.getLogger(GestionModulos.class.getName()).log(Level.SEVERE, null, e);
        }

        // Cierra el Statement
        try {
            if (stmt != null) {
                stmt.close();
                stmt = null;
            }
        } catch (SQLException | NullPointerException e) {
            Logger.getLogger(GestionModulos.class.getName()).log(Level.SEVERE, null, e);
        }

        // Cierra la conexión
        try {
            if (con != null) {
                con.close();
                con = null;
            }
        } catch (SQLException | NullPointerException e) {
            Logger.getLogger(GestionModulos.class.getName()).log(Level.SEVERE, null, e);
        }
    }

    private void terminarPrograma() {
        setVisible(false);
        dispose();
        System.exit(0);
    }

    private void insertarFila(ResultSet rs) throws SQLException {
        // Mueve el cursor a la fila de insercion
        rs.moveToInsertRow();

        // Actualiza las columnas del ResultSet
        rs.updateInt(1, Integer.parseInt(modulo.getId()));
        rs.updateString(2, modulo.getAcronimo());
        rs.updateString(3, modulo.getNombre());
        rs.updateString(4, modulo.getCodigo());
        rs.updateInt(5, modulo.getHoras());
        rs.updateInt(6, modulo.isCurso1() ? 1 : 2);

        // Inserta la fila de inserción
        rs.insertRow();

        // Sale de la zona de inserción a la zona previa
        rs.moveToCurrentRow();
    }

    private void actualizarFila(ResultSet rs) throws SQLException {
        // Actualiza las columnas del ResultSet
        rs.updateInt(1, Integer.parseInt(modulo.getId()));
        rs.updateString(2, modulo.getAcronimo());
        rs.updateString(3, modulo.getNombre());
        rs.updateString(4, modulo.getCodigo());
        rs.updateInt(5, modulo.getHoras());
        rs.updateInt(6, modulo.isCurso1() ? 1 : 2);

        // Actualiza la fila actual
        rs.updateRow();
    }

    private boolean validarCampoTexto(JTextField txfActual, String expReg,
            String textoCampoVacio) throws PatternSyntaxException {
        // Semáforo de validación
        boolean validacionOK = false;

        try {
            // Obtiene el texto del campo
            String textoActual = txfActual.getText().trim();

            // Comprueba campo vacio
            if (textoActual.equals("")) {
                textoActual = textoCampoVacio;
            }

            // Restaura el texto formateado
            txfActual.setText(textoActual);

            // Patrón de la expresión regular
            Pattern patron = Pattern.compile(expReg);

            // Aplica el patrón al texto de prueba
            Matcher detector = patron.matcher(textoActual);

            // Comprueba la coincidencia
            if (detector.matches()) {
                // Contenido correcto
                txfActual.setForeground(Color.BLACK);

                // Actualiza el semáforo
                validacionOK = true;
            } else {
                // Contenido problemático
                txfActual.setForeground(Color.RED);
            }
        } catch (PatternSyntaxException pse) {
            // Contenido problemático
            txfActual.setForeground(Color.RED);

            // Expresión regular errónea - Depurar expresión
            JOptionPane.showMessageDialog(this, "Expresión regular errónea");
        }

        // Devuelve el resultado de la validacion
        return validacionOK;
    }

    private boolean comprobarValidez() {
        // Validar id
        idOK = validarCampoTexto(txfId, ER_ID, "*");

        // Validar acrónimo
        acronimoOK = validarCampoTexto(txfAcronimo, ER_ACRONIMO, "*");

        // Validar nombre
        nombreOK = validarCampoTexto(txfNombre, ER_NOMBRE, "*");

        // Validar codigo
        codigoOK = validarCampoTexto(txfCodigo, ER_CODIGO, "*");

        return idOK && acronimoOK && nombreOK && codigoOK;
    }

    private int calcularNumeroRegistros(ResultSet rs) {
        // Variable para almacenar el resultado
        int numFilas;

        try {
            // Número de la fila a la que apunta el cursor
            int filaAct = rs.getRow();

            // Va al final del ResultSet
            rs.last();

            // Obtiene el número de filas
            numFilas = rs.getRow();

            if (filaAct != 0) {
                // Coloca el cursor en la posición previa
                rs.absolute(filaAct);
            } else {
                // Coloca el cursor al principio del ResultSet
                rs.first();
            }
        } catch (SQLException e) {
            numFilas = 0;
        }

        // Devuelve el número de filas calculadas
        return numFilas;
    }

    private int calcularPosicionActual(ResultSet rs) {
        int filaActual;

        try {
            filaActual = rs.getRow();
        } catch (SQLException | NullPointerException e) {
            filaActual = 0;
        }

        return filaActual;
    }
}
